package city.spring.configure;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import org.joda.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.io.IOException;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.function.BiFunction;
import java.util.function.Function;

import static city.spring.utils.DateUtils.*;

/**
 * 日期转换配置。
 * 在 SpringBoot 中，日期类型数据转换需要配置两个位置，一个是 SpringMVC 的日期类型参数转换，另一个是 Jackson 的 json 对象的日期类型参数转换。
 * 因此，需要配置 SpringMVC 的 Converter 对象，以及在 Jackson 的 Module 中配置 JsonSerializer 和 JsonDeserializer
 *
 * @author HouKunLin
 * @date 2020/3/17 0017 13:56
 */
@Configuration
public class CustomDateConverterConfiguration {
    private static final Logger logger = LoggerFactory.getLogger(CustomDateConverterConfiguration.class);

    /**
     * Jackson 的 ObjectMapper 对象
     */
    private final ObjectMapper objectMapper;

    @Value("${spring.jackson.date-format:yyyy-MM-dd HH:mm:ss}")
    private String pattern;

    public CustomDateConverterConfiguration(ObjectMapper objectMapper) {
        this.objectMapper = objectMapper;
    }

    @PostConstruct
    public void postConstruct() {
        SimpleModule module = new SimpleModule();
        // 增强的 Date 反序列化，使其支持更多的格式信息
        module.addDeserializer(Date.class, new JsonDeserializer<Date>() {
            @Override
            public Date deserialize(JsonParser jsonParser, DeserializationContext context) throws IOException {
                JsonNode node = jsonParser.getCodec().readTree(jsonParser);
                return convertDate(node.textValue());
            }
        });
        // Joda DateTime 序列化，把它转换为 Date 对象，然后由默认的 Date 序列化处理器来处理
        module.addSerializer(DateTime.class, new JsonSerializer<DateTime>() {
            @Override
            public void serialize(DateTime value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
                gen.writeObject(value.toDate());
            }
        });
        // Joda DateTime 反序列化
        module.addDeserializer(DateTime.class, new JsonDeserializer<DateTime>() {
            @Override
            public DateTime deserialize(JsonParser jsonParser, DeserializationContext ctxt) throws IOException {
                JsonNode node = jsonParser.getCodec().readTree(jsonParser);
                return convertDateTime(node.textValue());
            }
        });
        module.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(pattern)));
        module.addDeserializer(LocalDateTime.class, new JsonDeserializer<LocalDateTime>() {
            @Override
            public LocalDateTime deserialize(JsonParser jsonParser, DeserializationContext ctxt) throws IOException {
                JsonNode node = jsonParser.getCodec().readTree(jsonParser);
                LocalDateTime localDateTime = null;
                try {
                    // 先尝试使用默认的时间格式进行反序列化
                    localDateTime = parseDate(node.textValue(), LocalDateTime::parse, LocalDateTime::parse);
                } catch (Exception e) {
                    try {
                        // 如果前面的反序列化失败，则尝试使用 joda time 来解析时间对象
                        DateTime dateTime = convertDateTime(node.textValue());
                        if (dateTime != null) {
                            // 把 DateTime 转换为 DateTime
                            localDateTime = LocalDateTime.ofInstant(dateTime.toDate().toInstant(), ZoneId.systemDefault());
                        }
                    } catch (Exception ignore) {
                        throw e;
                    }
                }
                return localDateTime;
            }
        });
        objectMapper.registerModule(module);
        logger.debug("配置自定义日期时间格式转换：{}", this);
    }

    /**
     * 普通 Date 转换
     *
     * @see city.spring.utils.DateUtils#convertDate(String)
     */
    @Component
    public static class DateConverter implements Converter<String, Date> {
        @Override
        public Date convert(@NonNull String source) {
            return convertDate(source);
        }
    }

    /**
     * Joda 对象转换
     *
     * @see city.spring.utils.DateUtils#convertDateTime(String)
     */
    @Component
    public static class DateTimeConverter implements Converter<String, DateTime> {
        @Override
        public DateTime convert(@NonNull String source) {
            return convertDateTime(source);
        }
    }

    /**
     * 本地日期时间转换
     *
     * @see city.spring.utils.DateUtils#parseDate(String, Function, BiFunction)
     */
    @Component
    public static class LocalDateTimeConverter implements Converter<String, LocalDateTime> {
        @Override
        public LocalDateTime convert(@NonNull String source) {
            return parseDate(source, LocalDateTime::parse, LocalDateTime::parse);
        }
    }

    /**
     * 本地日期转换
     *
     * @see city.spring.utils.DateUtils#parseDate(String, Function, BiFunction)
     */
    @Component
    public static class LocalDateConverter implements Converter<String, LocalDate> {
        @Override
        public LocalDate convert(@NonNull String source) {
            return parseDate(source, LocalDate::parse, LocalDate::parse);
        }
    }

    /**
     * 本地时间转换
     *
     * @see city.spring.utils.DateUtils#parseDate(String, Function, BiFunction)
     */
    @Component
    public static class LocalTimeConverter implements Converter<String, LocalTime> {
        @Override
        public LocalTime convert(@NonNull String source) {
            return parseDate(source, LocalTime::parse, LocalTime::parse);
        }
    }
}
